package net.love5z.web.common.utils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


public class ObjectPropUtils {

    protected final static Log logger = LogFactory.getLog(ObjectPropUtils.class);

    /**
     * 
     * @param sourceCollection
     * @param targetCollection
     * @param targetClazz
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static void copyCollectionProperties(Collection sourceCollection,
                                                Collection targetCollection, Class targetClazz) {
        for (Object source : sourceCollection) {
            try {
                Object target = targetClazz.newInstance();
                copyProperties(source, target);

                targetCollection.add(target);
            } catch (InstantiationException e) {
                logger.warn("InstantiationException", e);
            } catch (IllegalAccessException e) {
                logger.warn("IllegalAccessException", e);
            }
        }
    }

    @SuppressWarnings("rawtypes")
    public static Object createAndCopyProperties(Object source, Class targetClass) {
        if (source == null) {
            return null;
        }
        Object target = null;
        try {
            target = targetClass.newInstance();
        } catch (InstantiationException e) {
            logger.warn("InstantiationException", e);
        } catch (IllegalAccessException e) {
            logger.warn("IllegalAccessException", e);
        }
        copyProperties(source, target);
        return target;
    }

    /**
     * copy readable properties from source and set to target
     * 
     * @param source
     * @param target
     */
    public static void copyProperties(Object source, Object target) {
        if (source == null) {
            return;
        }

        if (target == null) {
            return;
        }

        Map<String, Method> sourceGetMethodMap = retriveGetMethodMap(source);
        Map<String, Method> targetSetMethodMap = retriveSetMethodMap(target);

        for (Map.Entry<String, Method> sourceGetMethodEntry : sourceGetMethodMap.entrySet()) {
            Method sourceGetMethod = sourceGetMethodEntry.getValue();
            Method targetSetMethod = targetSetMethodMap.get(sourceGetMethodEntry.getKey());

            if (targetSetMethod != null) {
                try {
                    Object value = sourceGetMethod.invoke(source);

                    Class<?> setParamClass = targetSetMethod.getParameterTypes()[0];

                    if (setParamClass == String.class && !(value instanceof String)) {
                        value = value.toString();
                    }

                    if (isAssignableFrom(value.getClass(), setParamClass)) {
                        targetSetMethod.invoke(target, value);
                    }

                } catch (IllegalArgumentException e) {
                    logger.warn("IllegalArgumentException", e);
                } catch (IllegalAccessException e) {
                    logger.warn("IllegalAccessException", e);
                } catch (InvocationTargetException e) {
                    logger.warn("InvocationTargetException", e);
                }
            }
        }
    }

    private static Map<String, Method> retriveGetMethodMap(Object object) {
        Method[] methods = object.getClass().getDeclaredMethods();

        Map<String, Method> map = new HashMap<String, Method>();

        for (Method method : methods) {
            if (method.getName().startsWith("get")) {
                map.put(formatPropertyName(method.getName().substring(3)), method);
            }
        }

        return map;
    }

    private static Map<String, Method> retriveSetMethodMap(Object object) {
        Method[] methods = object.getClass().getDeclaredMethods();

        Map<String, Method> map = new HashMap<String, Method>();

        for (Method method : methods) {
            if (method.getName().startsWith("set")) {
                map.put(formatPropertyName(method.getName().substring(3)), method);
            }
        }

        return map;
    }

    private static String formatPropertyName(String propertyName) {
        propertyName = propertyName.replace("_", "");
        propertyName = propertyName.toLowerCase();

        return propertyName;
    }

    /**
     * Test if target can assignableFrom source
     * @param source
     * @param target
     * @return
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static boolean isAssignableFrom(Class source, Class target) {

        Map<Class, Class> map = new HashMap<Class, Class>();

        map.put(boolean.class, Boolean.class);
        map.put(byte.class, Byte.class);
        map.put(char.class, Character.class);
        map.put(short.class, Short.class);
        map.put(int.class, Integer.class);
        map.put(long.class, Long.class);
        map.put(float.class, Float.class);
        map.put(double.class, Double.class);

        if (target.isPrimitive() && !source.isPrimitive()) {
            return map.get(target).isAssignableFrom(source);
        }

        if (!target.isPrimitive() && source.isPrimitive()) {
            return target.isAssignableFrom(map.get(source));
        }

        return target.isAssignableFrom(source);
    }
}
